package org.carabiner.infinitest.swingui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Rectangle;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Pattern;

import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;

import org.carabiner.infinitest.FilterList;
import org.carabiner.infinitest.InfinitestCore;
import org.carabiner.infinitest.InfinitestSettings;
import org.carabiner.infinitest.depgraph.DependencyGraph;
import org.carabiner.infinitest.depgraph.JavaClass;
import org.carabiner.infinitest.depgraph.RegexVerifier;
import org.carabiner.infinitest.depgraph.TestVerifier;

/**
 * Swing based UI for Infinitest
 * 
 * @author ben
 *
 */
public class InfinitestSwingGuiMain implements PropertyChangeListener
{  
  private InfinitestCore core;
  
  private ResultTreeModel testResults;
  private JTree tree;
  private JFrame frame;
  private JProgressBar progressBar;    

  public InfinitestSwingGuiMain(File filterListFile, File path) throws IOException
  {
    frame = new JFrame();    
    frame.setAlwaysOnTop(true);
    frame.setLayout(new BorderLayout());
    testResults = new ResultTreeModel();
    tree = createTree();      
    frame.add(new JScrollPane(tree), BorderLayout.CENTER);
    progressBar = new JProgressBar();    
    progressBar.setForeground(Color.YELLOW);
    frame.add(progressBar, BorderLayout.SOUTH);
    testResults.addTreeModelListener(new TreeModelExpansionListener(tree));
    core = new InfinitestCore(filterListFile, true, path);
    core.addTestStatusListener(new EventNormalizer(testResults));    
    core.addPropertyChangeListener(this); 
        
    frame.add(createToolBar(), BorderLayout.WEST);
    frame.setTitle(InfinitestSettings.SCANNING_MSG);
    frame.setSize(480, 120);    
  }

  private Component createToolBar()
  {
    JToolBar toolbar = new JToolBar(JToolBar.VERTICAL);    
    toolbar.add(new ReloadIndexAction(core));    
    toolbar.add(new HaltTestAction(core));
    return toolbar;
  }

  private JTree createTree()
  {
    JTree resultsTree = new ResultsTree(testResults);
    resultsTree.setRootVisible(false);
    resultsTree.setEditable(false);
    resultsTree.setShowsRootHandles(true);
    resultsTree.setCellRenderer(new FailureCellRenderer());
    ToolTipManager tipManager = ToolTipManager.sharedInstance();
    tipManager.registerComponent(resultsTree);
    tipManager.setDismissDelay(10000);
    resultsTree.addMouseListener(new ClipboardCopyListener());
    return resultsTree;
  }
 
  JFrame getFrame()
  {
    return frame;
  }
  
  private void updateProgress(final int progress)
  {
    progressBar.setValue(progress);
    progressBar.setForeground(Color.YELLOW);    
  }
  
  /**
   * Can be called from any thread
   *
   */
  private void updateGUI()
  {
    // If this gets any more complex, it should be refactored to the state
    // pattern.
    Date timeStamp = new Date(System.currentTimeMillis());
    if (core.shouldRunTests())
    {
      if (testResults.getTestCaseCount() == 0)
      {
        frame.setTitle(InfinitestSettings.TESTS_PASSED_MSG + timeStamp);
        progressBar.setForeground(InfinitestSettings.PASSING_COLOR);
      }
      else
      {
        frame.setTitle(InfinitestSettings.FAILURES_DETECTED_MSG + timeStamp);
        progressBar.setForeground(Color.RED);
      }
    }
    else
    {
      frame.setTitle(InfinitestSettings.TESTS_PAUSED + timeStamp);
      progressBar.setForeground(Color.YELLOW);
    }
  }

  private void runThisOnEventThread(Runnable runnable)
  {
    if (!SwingUtilities.isEventDispatchThread())
    {
      try
      {
        SwingUtilities.invokeAndWait(runnable);
      }
      catch (InterruptedException e)
      {
        throw new RuntimeException(e);
      }
      catch (InvocationTargetException e)
      {
        throw new RuntimeException(e);
      }
    }
    else
      runnable.run();
  }
  
  public static void main(String[] args) throws IOException
  {
    File classFileDir = new File(args[0]);
    File filterListFile = null;
    if (args.length > 1)
      filterListFile = new File(args[1]);
    InfinitestSwingGuiMain app = new InfinitestSwingGuiMain(filterListFile, classFileDir);
    app.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);    
    app.frame.setVisible(true);
  }

  public FilterList getFilters()
  {
    return core.getFilters();
  }

  public void propertyChange(final PropertyChangeEvent evt)
  {
    Runnable runnable = new Runnable()
    {
      public void run()
      {
        if (evt.getPropertyName().equals(InfinitestCore.CURRENT_TEST_PROPERTY))
          frame.setTitle("Infinitest - Running Test: " + evt.getNewValue());
        if (evt.getPropertyName().equals(InfinitestCore.RUN_TESTS_PROPERTY))
          updateGUI();
        if (evt.getPropertyName().equals(InfinitestCore.TESTS_IN_QUEUE_PROPERTY))
        {
          if (evt.getOldValue().equals(0))
            progressBar.setMaximum((Integer)evt.getNewValue());
          int testsLeftToRun = progressBar.getMaximum() - (Integer)evt.getNewValue();
          updateProgress(testsLeftToRun);
          updateGUI();
        }        
      }
    };
    runThisOnEventThread(runnable);
  }

  public void run()
  {
    core.update();    
  }

  public JTree getTree()
  {
    return tree;
  }
}
